import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { initCameraParams } from "@/views/common";

/**
 * 创建一个球体来显示可视化位置
 * @param {*} pos:需要显示的位置，例如：new THREE.Vector3(24,2,10)
 * @param {*} scene three场景对象
 * @param {*} radius 球体半径，不传入为 0.1
 * @param {*} color 球体颜色，不传入为红色 #ff0000
 */
export function viewPos(pos, scene, radius, color) {
  const geometry = new THREE.SphereGeometry(radius ? radius : 0.1);
  const material = new THREE.MeshBasicMaterial({
    color: color ? color : "#ff0000",
  });
  const mesh = new THREE.Mesh(geometry, material);
  mesh.position.copy(pos);
  scene.add(mesh);
  return mesh;
}

/**
 * 创建一个箭头来指示出方向
 * @param {*} end：结束点，箭头将从start指向这里，例如：new THREE.Vector3(24,2,10)
 * @param {*} color 箭头颜色，不传入为红色 #ff0000
 * @param {*} strat 起始点，不传入为原点 new THREE.Vector3(0, 0, 0)
 */
export function viewDir(end, color, strat) {
  let str = strat ? strat : new THREE.Vector3(0, 0, 0);
  const dir = end.clone().sub(str).normalize();
  const length = end.clone().sub(str).length();
  const arrowHelper = new THREE.ArrowHelper(
    dir,
    str,
    length,
    color ? color : "#ff0000"
  );
  return arrowHelper;
}

/**
 * 清除threejs创建的对象，释放对象以及后代的几何体、材质纹理等资源
 * @param {*} obj:需要清除释放的threejs对象
 * @param {*} all:是否清除所有，all为false时只会清理obj后代资源，保留obj对象
 */
export function disposeObject(obj, all = false) {
  if (!obj) return;

  const materialMaps = [
    "map",
    "alphaMap",
    "aoMap",
    "bumpMap",
    "displacementMap",
    "emissiveMap",
    "envMap",
    "lightMap",
    "metalnessMap",
    "normalMap",
    "roughnessMap",
    "specularMap",
  ];

  if (obj.traverse) {
    obj.traverse((item) => {
      // 释放几何体
      if (item.geometry) {
        item.geometry.dispose();
      }

      // 释放材质和纹理
      if (item.material) {
        if (Array.isArray(item.material)) {
          item.material.forEach((material) => {
            materialMaps.forEach((mapName) => {
              if (material[mapName]) {
                material[mapName].dispose();
              }
            });
            material.dispose();
          });
        } else {
          materialMaps.forEach((mapName) => {
            if (item.material[mapName]) {
              item.material[mapName].dispose();
            }
          });
          item.material.dispose();
        }
      }

      // 清除CSS2D/3D对象dom元素
      if (item.isCSS2DObject || item.isCSS3DObject) {
        if (item.element && item.element.parentNode) {
          item.element.parentNode.removeChild(item.element);
        }
      }
    });
  }
  obj.children = [];

  if (all) {
    // 清理自身
    if (obj.dispose) {
      obj.dispose();
    }
    obj = null;
  }
}

/**
 * 异步加载gltf模型
 * @param {*} url:模型地址，模型需要放置在public文件下
 */
export function loadGLTFModel(url) {
  return new Promise((resolve, reject) => {
    const loader = new GLTFLoader();
    loader.load(
      url,
      (gltf) => {
        resolve(gltf.scene);
      },
      undefined,
      (error) => {
        reject(error);
      }
    );
  });
}

/**
 * 异步加载纹理贴图
 * @param {*} url:纹理贴图地址，纹理贴图需要放置在public文件下
 */
export function loadTexture(url) {
  return new Promise((resolve, reject) => {
    const loader = new THREE.TextureLoader();
    loader.load(
      url,
      (texture) => {
        resolve(texture);
      },
      undefined,
      (error) => {
        reject(error);
      }
    );
  });
}

/**
 * 检测一个位置是否在地图模型中，返回检测到的子地图模型
 * @param {*} center:二维位置数组，由经纬度坐标转换而来，例如：[12.31,22.12]
 * @param {*} mapModel:检测的地图模型
 */
export function testMap(center, mapModel, camera) {
  // 创建射线起点和方向
  const rayOrigin = new THREE.Vector3(center[0], 1, center[1]);
  const rayDirection = new THREE.Vector3(0, -1, 0);

  // 更新射线起点和方向，根据模型的变换矩阵
  rayOrigin.applyMatrix4(mapModel.matrixWorld);
  rayDirection.transformDirection(mapModel.matrixWorld);

  // 创建射线
  const raycaster = new THREE.Raycaster(rayOrigin, rayDirection);
  raycaster.camera = camera;
  // 更新模型的世界矩阵
  mapModel.updateMatrixWorld(true);

  // 射线检测
  let intersects = raycaster.intersectObjects(mapModel.children[0].children);

  // 检测结果筛选
  intersects = intersects.filter(function (intersect) {
    return (
      intersect.object.parent.name !== "边线-发光" &&
      intersect.object.name !== "光圈" &&
      intersect.object.name !== "光柱" &&
      intersect.object.name !== "描边模型" &&
      intersect.object.name !== "合并模型"
    );
  });
  return intersects;
}

export function calculateModelSize(object) {
  const box = new THREE.Box3().setFromObject(object);
  const size = new THREE.Vector3();
  box.getSize(size);
  return size;
}

// 根据区域编码判断行政管理级别
export function getAdministrativeLevel(code) {
  const cityCode = code.substring(2, 4);
  const districtCode = code.substring(4, 6);

  if (code === "100000") {
    return "国";
  } else if (cityCode === "00" && districtCode === "00") {
    return "省";
  } else if (districtCode === "00") {
    return "市";
  } else {
    return "县";
  }
}

export function resetOrigin(model, offset, status) {
  const scaledBox = new THREE.Box3().setFromObject(model);
  const center = new THREE.Vector3();
  scaledBox.getCenter(center);
  if (offset) {
    for (let i in offset) {
      center[i] += offset[i];
    }
  }
  model.position.sub(center);
  if (status == "省") {
    model.position.y += center.y;
  } else {
    model.position.y = 0;
  }
}

export function setCameraPosition(cameraParams, camera, controls) {
  const yawAngle = THREE.MathUtils.degToRad(cameraParams.yawAngle);
  const highAngle = THREE.MathUtils.degToRad(cameraParams.highAngle);
  camera.position.x =
    cameraParams.height * Math.cos(highAngle) * Math.sin(yawAngle);
  camera.position.y = cameraParams.height * Math.sin(highAngle);
  camera.position.z =
    cameraParams.height * Math.cos(highAngle) * Math.cos(yawAngle);
  camera.position.x += cameraParams.offsetX;
  camera.position.y += 0;
  camera.position.z += cameraParams.offsetZ;
  controls.target.set(
    cameraParams.offsetX,
    0,
    cameraParams.offsetZ
  );
  controls.update();
}

// 重置相机配置参数
export function resetCameraParams(cameraParams, camera, controls) {
  cameraParams = initCameraParams;
  setCameraPosition(cameraParams, camera, controls);
}
